// Go作者版权所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package modload

import (
	"errors"
	"fmt"
	"io/fs"
	"os"
	"path/filepath"
	"strings"
	"sync"

	"cmd/go/internal/base"

	"golang.org/x/mod/module"
	"golang.org/x/mod/semver"
)

var (
	vendorOnce      sync.Once
	vendorList      []module.Version          // 按外观顺序为构建提供包的模块
	vendorReplaced  []module.Version          // 所有更换的模块；可能会也可能不会提供软件包
	vendorVersion   map[string]string         // 模块路径→ 所选版本（如果已知）
	vendorPkgModule map[string]module.Version // 包裹→ 包含模块
	vendorMeta      map[module.Version]vendorMetadata
)

type vendorMetadata struct {
	Explicit    bool
	Replacement module.Version
	GoVersion   string
}

// readVendorList从vendor/modules.txt读取供应商模块列表。
func readVendorList() {
	vendorOnce.Do(func() {
		vendorList = nil
		vendorPkgModule = make(map[string]module.Version)
		vendorVersion = make(map[string]string)
		vendorMeta = make(map[module.Version]vendorMetadata)
		data, err := os.ReadFile(filepath.Join(ModRoot(), "vendor/modules.txt"))
		if err != nil {
			if !errors.Is(err, fs.ErrNotExist) {
				base.Fatalf("go: %s", err)
			}
			return
		}

		var mod module.Version
		for _, line := range strings.Split(string(data), "\n") {
			if strings.HasPrefix(line, "# ") {
				f := strings.Fields(line)

				if len(f) < 3 {
					continue
				}
				if semver.IsValid(f[2]) {
					// 一个模块，但我们还不知道它是否在构建列表中
					// 仅用于表示更换。
					mod = module.Version{Path: f[1], Version: f[2]}
					f = f[3:]
				} else if f[2] == "=>" {
					// 在主模块的go.mod文件中找到一个通配符替换项。
					mod = module.Version{Path: f[1]}
					f = f[2:]
				} else {
					// 不是版本或通配符替换。
					// 我们不知道如何解释这个模块行，所以忽略它。
					mod = module.Version{}
					continue
				}

				if len(f) >= 2 && f[0] == "=>" {
					meta := vendorMeta[mod]
					if len(f) == 2 {
						// 文件替换。
						meta.Replacement = module.Version{Path: f[1]}
						vendorReplaced = append(vendorReplaced, mod)
					} else if len(f) == 3 && semver.IsValid(f[2]) {
						// 路径和版本替换。
						meta.Replacement = module.Version{Path: f[1], Version: f[2]}
						vendorReplaced = append(vendorReplaced, mod)
					} else {
						// 我们不理解这种替换。别理它。
					}
					vendorMeta[mod] = meta
				}
				continue
			}

			// 不是模块线。必须是模块或元数据中的包
			// 指令，其中任何一个都需要前面的模块行。
			if mod.Path == "" {
				continue
			}

			if strings.HasPrefix(line, "## ") {
				// 元数据。跨多行合并注释（如果存在）。
				meta := vendorMeta[mod]
				for _, entry := range strings.Split(strings.TrimPrefix(line, "## "), ";") {
					entry = strings.TrimSpace(entry)
					if entry == "explicit" {
						meta.Explicit = true
					}
					if strings.HasPrefix(entry, "go ") {
						meta.GoVersion = strings.TrimPrefix(entry, "go ")
						rawGoVersion.Store(mod, meta.GoVersion)
					}
					// 所有其他代币保留供将来使用。
				}
				vendorMeta[mod] = meta
				continue
			}

			if f := strings.Fields(line); len(f) == 1 && module.CheckImportPath(f[0]) == nil {
				// 当前模块中的包。
				vendorPkgModule[f[0]] = mod

				// 因为这个模块为构建提供了一个包，所以我们知道它
				// 位于生成列表中，并且是其路径的选定版本。
				// 如果此信息是新的，请记录它。
				if v, ok := vendorVersion[mod.Path]; !ok || semver.Compare(v, mod.Version) < 0 {
					vendorList = append(vendorList, mod)
					vendorVersion[mod.Path] = mod.Version
				}
			}
		}
	})
}

// CheckVendor一致性验证vendor/modules.txt文件是否匹配（如果
// go 1.14）或至少与（go 1.13或更早）不矛盾
// 主模块的go.mod文件中列出的要求和替换。
func checkVendorConsistency() {
	readVendorList()

	pre114 := false
	if semver.Compare(index.goVersionV, "v1.14") < 0 {
		// Go 1.14之前的版本中没有包含足够的信息
		// 用于检查一致性的vendor/modules.txt。
		// 如果我们知道我们使用的是早期版本，请放松一致性检查。
		pre114 = true
	}

	vendErrors := new(strings.Builder)
	vendErrorf := func(mod module.Version, format string, args ...interface{}) {
		detail := fmt.Sprintf(format, args...)
		if mod.Version == "" {
			fmt.Fprintf(vendErrors, "\n\t%s: %s", mod.Path, detail)
		} else {
			fmt.Fprintf(vendErrors, "\n\t%s@%s: %s", mod.Path, mod.Version, detail)
		}
	}

	// 以原始（未索引）顺序迭代Require指令
	// 使错误与原始文件匹配。
	for _, r := range modFile.Require {
		if !vendorMeta[r.Mod].Explicit {
			if pre114 {
				// 在1.14之前，modules.txt没有指明是否列出了模块
				// 在主模块的go.mod文件中显式显示。
				// 然而，如果包被删除，我们至少可以检测到版本不匹配
				// 由不匹配的版本提供。
				if vv, ok := vendorVersion[r.Mod.Path]; ok && vv != r.Mod.Version {
					vendErrorf(r.Mod, fmt.Sprintf("is explicitly required in go.mod, but vendor/modules.txt indicates %s@%s", r.Mod.Path, vv))
				}
			} else {
				vendErrorf(r.Mod, "is explicitly required in go.mod, but not marked as explicit in vendor/modules.txt")
			}
		}
	}

	describe := func(m module.Version) string {
		if m.Version == "" {
			return m.Path
		}
		return m.Path + "@" + m.Version
	}

	// 我们需要验证modfile中发生的*所有*替换：即使它们
	// 不要直接应用于供应商列表中的任何模块，更换
	// go.mod文件可能会影响其他文件的选定版本（可传递）
	// 依赖关系
	for _, r := range modFile.Replace {
		vr := vendorMeta[r.Old].Replacement
		if vr == (module.Version{}) {
			if pre114 && (r.Old.Version == "" || vendorVersion[r.Old.Path] != r.Old.Version) {
				// 在1.14之前，modules.txt省略了通配符替换和
				// 对供应商没有任何软件包的模块进行更换。
			} else {
				vendErrorf(r.Old, "is replaced in go.mod, but not marked as replaced in vendor/modules.txt")
			}
		} else if vr != r.New {
			vendErrorf(r.Old, "is replaced by %s in go.mod, but marked as replaced by %s in vendor/modules.txt", describe(r.New), describe(vr))
		}
	}

	for _, mod := range vendorList {
		meta := vendorMeta[mod]
		if meta.Explicit {
			if _, inGoMod := index.require[mod]; !inGoMod {
				vendErrorf(mod, "is marked as explicit in vendor/modules.txt, but not explicitly required in go.mod")
			}
		}
	}

	for _, mod := range vendorReplaced {
		r := Replacement(mod)
		if r == (module.Version{}) {
			vendErrorf(mod, "is marked as replaced in vendor/modules.txt, but not replaced in go.mod")
			continue
		}
		if meta := vendorMeta[mod]; r != meta.Replacement {
			vendErrorf(mod, "is marked as replaced by %s in vendor/modules.txt, but replaced by %s in go.mod", describe(meta.Replacement), describe(r))
		}
	}

	if vendErrors.Len() > 0 {
		base.Fatalf("go: inconsistent vendoring in %s:%s\n\n\tTo ignore the vendor directory, use -mod=readonly or -mod=mod.\n\tTo sync the vendor directory, run:\n\t\tgo mod vendor", modRoot, vendErrors)
	}
}
